//! # msoffice crypt lib
//!
//! [msoffice-crypt](https://github.com/herumi/msoffice) bindings for the Rust programming language.
//!
//! A lib to encrypt/decrypt Microsoft Office Document
//!
//! ## requirement
//!
//! you must add the lib libmsoc.so to the system lib before run the bin app.
//!
//! 1.linux:
//!
//! [libmsoc.so](https://gitee.com/eshangrao/msoffice-crypt-rust/tree/master/lib)
//! [you must compile the lib youself , read more](https://github.com/herumi/msoffice)
//! 
//! ```bash
//! export LD_LIBRARY_PATH=some/path/to/libmsoc.so/dir:$LD_LIBRARY_PATH
//! ```
//!
//! 2.windows:
//! 
//! [msoc.dll](https://gitee.com/eshangrao/msoffice-crypt-rust/tree/master/lib)
//! 
//! ```bash
//! set PATH=some\path\to\msoc.dll\dir;%PATH%
//! ```
//! ## example
//!
//! ```rust
//! use msoffice_crypt::{encrypt,decrypt};
//! fn main() {
//!         # encrypt the input to output file
//!         let input = "/home/feiy/Desktop/1.xlsx";
//!         let output = "/home/feiy/Desktop/output.xlsx";
//!         let password = "test";
//!         let ret = encrypt(input,password,Some(output));
//!         println!("{ret:#?}");
//!         # decrypt the input file to output file
//!         let plain = "/home/feiy/Desktop/plain.xlsx";
//!         let ret = decrypt(output,password,Some(plain));
//!         println!("{ret:#?}");
//!         // overwrite the input file
//!         let plain = "/home/feiy/Desktop/plain.xlsx";
//!         let ret = encrypt(plain,password,None);
//!         println!("{ret:#?}");
//!         
//! }
//! ```
use std::error::Error;
use std::ffi::c_int;
use std::ffi::CString;
use std::fmt::Display;
use std::path::Path;
use std::{os::raw::c_char, ptr::null};
#[link(name = "msoc")]
extern "C" {
    fn MSOC_encryptA(
        outFile: *const c_char,
        inFile: *const c_char,
        pass: *const c_char,
        opt: *const usize,
    ) -> c_int;
    fn MSOC_decryptA(
        outFile: *const c_char,
        inFile: *const c_char,
        pass: *const c_char,
        opt: *const usize,
    ) -> c_int;
}
#[derive(Debug, PartialEq, Eq)]
pub enum CryptError {
    InputFileNotExist,
    NotSupportFormat,
    AlreadyEncryped,
    BadPassword,
    UnknownError(i32),
}
impl Display for CryptError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CryptError::InputFileNotExist => f.write_str("The input file is not exist!"),
            CryptError::NotSupportFormat => {
                f.write_str("The input file's format is not supported!")
            }
            CryptError::AlreadyEncryped => f.write_str("The input file had encryped!"),
            CryptError::BadPassword => f.write_str("The password is incorrect"),
            CryptError::UnknownError(ret) => write!(f, "unknown error,ret code {ret}"),
        }
    }
}
impl From<CryptError> for String {
    fn from(value: CryptError) -> Self {
        value.to_string()
    }
}
impl Error for CryptError {}

/// encrypt inFile and make outFile with pass(ASCII version : not UTF-8)
///
/// # parameters:
///
/// * in_file : The plain MS Office file to be encrypted
/// * password : encrypt password
/// * out_file : The encrypted MS Office file, use the in_file as out_file if the param is None .
pub fn encrypt(in_file: &str, password: &str, out_file: Option<&str>) -> Result<(), CryptError> {
    if !Path::new(in_file).exists() {
        return Err(CryptError::InputFileNotExist);
    }
    unsafe {
        let input = CString::new(in_file).unwrap();
        let output = CString::new(out_file.unwrap_or(in_file)).unwrap();
        let password = CString::new(password).unwrap();
        let ret = MSOC_encryptA(output.as_ptr(), input.as_ptr(), password.as_ptr(), null());
        match ret {
            0 => Ok(()),
            1 => Err(CryptError::NotSupportFormat),
            2 => Err(CryptError::AlreadyEncryped),
            3 => Err(CryptError::BadPassword),
            8 => Err(CryptError::InputFileNotExist),
            _ => Err(CryptError::UnknownError(ret)),
        }
    }
}

/// decrypt inFile and make outFile with pass(ASCII version : not UTF-8)
///
/// # parameters:
///
/// * in_file : The encrypted MS Office file
/// * password : decrypt password
/// * out_file : The plain MS Office file (NULL is permitted), use the in_file as out_file if the param is None .
pub fn decrypt(in_file: &str, password: &str, out_file: Option<&str>) -> Result<(), CryptError> {
    if !Path::new(in_file).exists() {
        return Err(CryptError::InputFileNotExist);
    }
    unsafe {
        let input = CString::new(in_file).unwrap();
        let output = CString::new(out_file.unwrap_or(in_file)).unwrap();
        let password = CString::new(password).unwrap();
        let ret = MSOC_decryptA(output.as_ptr(), input.as_ptr(), password.as_ptr(), null());
        match ret {
            0 => Ok(()),
            1 => Err(CryptError::NotSupportFormat),
            2 => Err(CryptError::AlreadyEncryped),
            3 => Err(CryptError::BadPassword),
            8 => Err(CryptError::InputFileNotExist),
            _ => Err(CryptError::UnknownError(ret)),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_encrypt() {
        let input = "/home/feiy/Desktop/1.xlsx";
        let output = "/home/feiy/Desktop/output.xlsx";
        let password = "test";
        let ret = encrypt(input, password, Some(output));
        assert_eq!(ret, Ok(()));
    }
    #[test]
    fn test_decrypt() {
        let input = "/home/feiy/Desktop/output.xlsx";
        let password = "test";
        let ret = decrypt(input, password, None);
        assert_eq!(ret, Ok(()));
    }
}
